/*
 * Galaxium Messenger
 * Copyright (C) 2003-2007 Philippe Durand <draekz@gmail.com>
 * Copyright (C) 2007 Ben Motmans <ben.motmans@gmail.com>
 * Copyright (C) 2007 Paul Burton <paulburton89@gmail.com>
 * 
 * License: GNU General Public License (GPL)
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

using System;
using System.Collections.Generic;
using System.Text;

using Anculus.Core;

using Galaxium.Core;

namespace Galaxium.Protocol.Msn
{
	public abstract class AbstractMsnCommand: IMsnCommand
	{
		MsnSession _session;
		int _trid = -1;
		List<string> _arguments = new List<string> ();
		bool _incoming = false;
		MsnCommandAttribute _cmdAttribute;
		TransactionCommandAttribute _transAttribute;
		PayloadCommandAttribute _payloadAttribute;
		ExpectResponseAttribute _expectResponseAttribute;
		
		public virtual string Command
		{
			get { return _cmdAttribute.Command; }
		}
		
		public int TransactionID
		{
			get { return _trid; }
			set { _trid = value; }
		}
		
		public List<string> Arguments
		{
			get { return _arguments; }
			protected set { _arguments = value; }
		}
		
		public virtual byte[] Payload
		{
			get { return null; }
			set { }
		}
		
		public MsnSession Session
		{
			get { return _session; }
		}
		
		public bool Incoming
		{
			get { return _incoming; }
		}
		
		public virtual bool IsPayloadIn
		{
			get { return (_payloadAttribute != null) && _payloadAttribute.IsIn; }
		}
		
		public virtual bool IsPayloadOut
		{
			get { return (_payloadAttribute != null) && _payloadAttribute.IsOut; }
		}
		
		public virtual bool IsTransIn
		{
			get { return (_transAttribute != null) && _transAttribute.IsIn; }
		}
		
		public virtual bool IsTransOut
		{
			get { return (_transAttribute != null) && _transAttribute.IsOut; }
		}
		
		public virtual bool ExpectResponse
		{
			get { return _expectResponseAttribute != null; }
		}
		
		public AbstractMsnCommand (MsnSession session)
		{
			_session = session;
			
			foreach (MsnCommandAttribute att in GetType ().GetCustomAttributes (typeof (MsnCommandAttribute), false))
				_cmdAttribute = att;
			foreach (TransactionCommandAttribute att in GetType ().GetCustomAttributes (typeof (TransactionCommandAttribute), false))
				_transAttribute = att;
			foreach (PayloadCommandAttribute att in GetType ().GetCustomAttributes (typeof (PayloadCommandAttribute), false))
				_payloadAttribute = att;
			foreach (ExpectResponseAttribute att in GetType ().GetCustomAttributes (typeof (ExpectResponseAttribute), false))
				_expectResponseAttribute = att;

			ThrowUtility.ThrowIfNull ("cmdAttribute", _cmdAttribute);
		}
		
		public AbstractMsnCommand (MsnSession session, byte[] data)
			: this (session)
		{
			_incoming = true;
			
			int lineLen = MsnByteArrayUtility.IndexOf (data, "\r\n");
			
			if (lineLen < 0)
				lineLen = data.Length;
			
			byte[] lineData = new byte[lineLen];
			Array.Copy (data, lineData, lineLen);
			
			string line = Encoding.UTF8.GetString (lineData);
			string[] args = line.Split (new char[] { ' ' });
			
			if ((!string.IsNullOrEmpty (Command)) && (!(this is ErrorCommand)))
				ThrowUtility.ThrowIfFalse (string.Format ("Wrong command ({0} not {1})", args[0], Command), args[0] == Command);
			
			int i = 1;
			
			if (IsTransIn)
				_trid = int.Parse (args[i++]);
			
			// We need to provide arguments before we can get a correct value for IsPayloadIn
			// eg. ListCommand needs to know if the first argument is OK
			for (; i < args.Length; i++)
				_arguments.Add (args[i]);
			
			int argEnd = args.Length - (IsPayloadIn ? 1 : 0);
			
			// Now IsPayloadIn is ok and we know the correct number of arguments
			
			if (argEnd != args.Length)
			{
				// This is a payload command, so we need to remove the last item (payload length)
				_arguments.RemoveAt (_arguments.Count - 1);
				i--;
			}
			
			if (IsPayloadIn)
			{
				int payloadLen = int.Parse (args[i]);
				
				// Multipacket messaging means MSG commands can have the wrong payload length
				if (this is MSGCommand)
					payloadLen = data.Length - lineLen - 2;
				
				ThrowUtility.ThrowIfFalse (string.Format ("Wrong payload length ({0} not {1})", data.Length - lineLen - 2, payloadLen), data.Length - lineLen - 2 == payloadLen);
				
				byte[] payloadData = new byte[payloadLen];
				
				Array.Copy(data, lineLen + 2, payloadData, 0, payloadLen);
				
				Payload = payloadData;
			}
		}
		
		public virtual string ToCommandString ()
		{
			StringBuilder sb = new StringBuilder();
			
			sb.Append (Command);
			
			if ((_incoming && IsTransIn) || ((!_incoming) && IsTransOut))
				sb.Append (" " + _trid.ToString ());
			
			foreach (string arg in Arguments)
				sb.Append (" " + arg);
			
			if ((_incoming && IsPayloadIn) || ((!_incoming) && IsPayloadOut))
			{
				if (Payload != null)
					sb.Append (" " + Payload.Length.ToString ());
				else
					sb.Append (" 0");
			}
			
			return sb.ToString ();
		}
		
		public virtual byte[] ToByteArray ()
		{
			MessageBuilder mb = new Galaxium.Protocol.MessageBuilder ();
			
			mb.Append (ToCommandString ());
			mb.Append ("\r\n");
			
			if ((_incoming && IsPayloadIn) || ((!_incoming) && IsPayloadOut))
				mb.Append (Payload);
			
			return mb.GetByteArray ();
		}
		
		public override string ToString ()
		{
			return ToCommandString ();
		}
		
		protected void SetArgument (int i, string val)
		{
			while (_arguments.Count <= i)
				_arguments.Add (string.Empty);
			
			_arguments[i] = val;
		}
		
		protected string GetArgument (int i)
		{
			if (_arguments.Count <= i)
				return string.Empty;
			
			return _arguments[i];
		}
	}
}
